using System;
using System.IO;
using System.Text;
using NFreeMarker.Cache;
using NFreeMarker.Template;
using NFreeMarker.Template.Utility;

namespace NFreeMarker.Core
{
    /**
     * An instruction that gets another template
     * and processes it within the current template.
     */
    public sealed class Include : TemplateElement // NOTE: Was internal
    {
        private readonly Expression _includedTemplateName;
        private readonly Expression _encodingExp;
        private readonly Expression _parseExp;
        private readonly string _encoding;
        private readonly bool _parse;
        private readonly string _templatePath;

        /**
         * @param template the template that this <tt>Include</tt> is a part of.
         * @param includedTemplateName the name of the template to be included.
         * @param encodingExp the encoding to be used or null, if it is a default.
         * @param parseExp whether the template should be parsed (or is raw text)
         */

        internal Include(Template.Template template,
                        Expression includedTemplateName,
                        Expression encodingExp,
                        Expression parseExp)
        {
            string templatePath1 = template.GetName();
            int lastSlash = templatePath1.LastIndexOf('/');
            _templatePath = lastSlash == -1 ? "" : templatePath1.Substring(0, lastSlash + 1);
            _includedTemplateName = includedTemplateName;
            if (encodingExp is StringLiteral)
            {
                _encoding = encodingExp.ToString();
                _encoding = _encoding.Substring(1, _encoding.Length - 2);
            }
            else
            {
                _encodingExp = encodingExp;
            }
            if (parseExp == null)
            {
                _parse = true;
            }
            else if (parseExp.IsLiteral())
            {
                try
                {
                    if (parseExp is StringLiteral)
                    {
                        _parse = StringUtil.GetYesNo(parseExp.GetStringValue(null));
                    }
                    else
                    {
                        try
                        {
                            _parse = parseExp.IsTrue(null);
                        }
                        catch (NonBooleanException)
                        {
                            throw new ParseException(
                                "Expected a boolean or string as the value of the parse attribute", parseExp);
                        }
                    }
                }
                catch (TemplateException)
                {
                    // evaluation of literals must not throw a TemplateException
                    //throw new UndeclaredThrowableException(e);
                    throw new NotImplementedException();
                }
            }
            else
            {
                _parseExp = parseExp;
            }
        }

        internal override void Accept(Environment env)
        {
            string templateNameString = _includedTemplateName.GetStringValue(env);
            if (templateNameString == null)
            {
                string msg = "Error " + GetStartLocation()
                             + "The expression " + _includedTemplateName + " is undefined.";
                throw new InvalidReferenceException(msg, env);
            }
            string enc = _encoding;
            if (_encoding == null && _encodingExp != null)
            {
                enc = _encodingExp.GetStringValue(env);
            }

            bool parse = _parse;
            if (_parseExp != null)
            {
                ITemplateModel tm = _parseExp.GetAsTemplateModel(env);
                if (tm == null)
                {
                    if (env.IsClassicCompatible())
                    {
                        parse = false;
                    }
                    else
                    {
                        AssertNonNull(tm, _parseExp, env);
                    }
                }
                if (tm is ITemplateScalarModel)
                {
                    parse = GetYesNo(EvaluationUtil.GetString((ITemplateScalarModel) tm, _parseExp, env));
                }
                else
                {
                    parse = _parseExp.IsTrue(env);
                }
            }

            Template.Template includedTemplate;
            try
            {
                templateNameString = TemplateCache.GetFullTemplatePath(env, _templatePath, templateNameString);
                includedTemplate = env.GetTemplateForInclusion(
                    templateNameString,
                    enc != null ? Encoding.GetEncoding(enc) : null,
                    parse);
            }
            catch (ParseException pe)
            {
                string msg = "Error parsing included template "
                             + templateNameString + "\n" + pe.Message;
                throw new TemplateException(msg, pe, env);
            }
            catch (IOException ioe)
            {
                string msg = "Error reading included file "
                             + templateNameString;
                throw new TemplateException(msg, ioe, env);
            }
            env.Include(includedTemplate);
        }

        public override string GetCanonicalForm()
        {
            var buf = new StringBuilder("<#include ");
            buf.Append(_includedTemplateName);
            if (_encoding != null)
            {
                buf.Append(" encoding=\"");
                buf.Append(_encodingExp.GetCanonicalForm());
                buf.Append("\"");
            }
            if (_parseExp != null)
            {
                buf.Append(" parse=" + _parseExp.GetCanonicalForm());
            }
            else if (!_parse)
            {
                buf.Append(" parse=false");
            }
            buf.Append("/>");
            return buf.ToString();
        }

        public override string GetDescription()
        {
            return "include " + _includedTemplateName;
        }

        private bool GetYesNo(string s)
        {
            try
            {
                return StringUtil.GetYesNo(s);
            }
            catch (ArgumentException)
            {
                throw new ParseException(
                    "Error " + GetStartLocation() +
                    "\nValue of include parse parameter " +
                    "must be boolean or one of these strings: " +
                    "\"n\", \"no\", \"f\", \"false\", \"y\", \"yes\", \"t\", \"true\"" +
                    "\nFound: " + _parseExp, _parseExp);
            }
        }

        /*
        boolean heedsOpeningWhitespace() {
            return true;
        }

        boolean heedsTrailingWhitespace() {
            return true;
        }
       */
    }
}